PDK#
gdsfactory includes a generic PDK, that you can use as an inspiration to create your own pdks.
The PDK allows you to register: - cell functions that return Components from a ComponentSpec (string, Component, ComponentFactory or dict). Also known as PCells (parametric cells). - cross_section functions that return CrossSection from a CrossSection Spec (string, CrossSection, CrossSectionFactory or dict). - layers that return a GDS Layer from a string, an int or a Tuple[int, int].
You can only have one active PDK at a time. Thanks to PDK you can access components, cross_sections or layers using a string.
Depending on the active pdk:
get_layerreturns a Layer from the registed layers.get_componentreturns a Component from the registered cells or containers.get_cross_sectionreturns a CrossSection from the registered cross_sections.
layers#
GDS layers are a tuple of two integer number gdslayer/gdspurpose
You can define all the layers from your PDK:
From a Klayout
lyp(layer properties file).From scratch, adding all your layers into a class.
Lets generate the layers definition code from a klayout lyp file.
[1]:
import gdsfactory as gf
from gdsfactory.layers import lyp_to_dataclass
from gdsfactory.config import PATH
print(lyp_to_dataclass(PATH.klayout_lyp))
2022-06-15 17:09:03.887 | INFO | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 5.10.3
from pydantic import BaseModel
from gdsfactory.types import Layer
class LayerMap(BaseModel):
1_12_1: Layer = (1, 12)
CAPACITOR: Layer = (42, 0)
DEEPETCH: Layer = (3, 6)
DEEPTRENCH: Layer = (4, 0)
DICING: Layer = (65, 0)
DRC_EXCLUDE: Layer = (67, 0)
DRC_MARKER: Layer = (205, 0)
DevRec: Layer = (68, 0)
ERROR_MARKER: Layer = (207, 0)
Errors: Layer = (66, 0)
FLOORPLAN: Layer = (64, 0)
FbrTgt: Layer = (81, 0)
GE: Layer = (5, 0)
GENPP: Layer = (26, 0)
GEPPP: Layer = (29, 0)
LABEL: Layer = (201, 0)
LABEL_INSTANCES: Layer = (206, 0)
LABEL_SETTINGS: Layer = (202, 0)
Lumerical: Layer = (733, 0)
M1: Layer = (41, 0)
M1TILES: Layer = (191, 0)
M2: Layer = (45, 0)
M3: Layer = (49, 0)
METALOPEN: Layer = (46, 0)
MH: Layer = (47, 0)
N: Layer = (20, 0)
NOTILE_M1: Layer = (71, 0)
NOTILE_M2: Layer = (72, 0)
NOTILE_M3: Layer = (73, 0)
NP: Layer = (22, 0)
NPP: Layer = (24, 0)
OXIDE_ETCH: Layer = (6, 0)
P: Layer = (21, 0)
PDPP: Layer = (27, 0)
PORT1: Layer = (101, 0)
PORT2: Layer = (102, 0)
PORT3: Layer = (103, 0)
PORT4: Layer = (104, 0)
PP: Layer = (23, 0)
PPP: Layer = (25, 0)
PinRec: Layer = (1, 10)
PinRecM: Layer = (1, 11)
SHALLOWETCH: Layer = (2, 6)
SILICIDE: Layer = (39, 0)
SIM_REGION: Layer = (100, 0)
SITILES: Layer = (190, 0)
SLAB150: Layer = (2, 0)
SLAB150CLAD: Layer = (2, 9)
SLAB90: Layer = (3, 0)
SLAB90CLAD: Layer = (3, 1)
SOURCE: Layer = (110, 0)
TE: Layer = (203, 0)
TEXT: Layer = (66, 0)
TM: Layer = (204, 0)
Text: Layer = (66, 0)
VIA1: Layer = (44, 0)
VIA2: Layer = (43, 0)
VIAC: Layer = (40, 0)
WGCLAD: Layer = (111, 0)
WGN: Layer = (34, 0)
WGNCLAD: Layer = (36, 0)
Waveguide: Layer = (1, 0)
XS_BOX: Layer = (300, 0)
XS_GE: Layer = (315, 0)
XS_M1: Layer = (304, 0)
XS_M2: Layer = (399, 0)
XS_MH: Layer = (306, 0)
XS_N: Layer = (320, 0)
XS_NPP: Layer = (321, 0)
XS_OVERLAY: Layer = (311, 0)
XS_OXIDE_M1: Layer = (305, 0)
XS_OXIDE_M2: Layer = (307, 0)
XS_OXIDE_M3: Layer = (311, 0)
XS_OXIDE_MH: Layer = (317, 0)
XS_OXIDE_ML: Layer = (309, 0)
XS_OX_SI: Layer = (302, 0)
XS_P: Layer = (330, 0)
XS_PDPP: Layer = (327, 0)
XS_PPP: Layer = (331, 0)
XS_SI: Layer = (301, 0)
XS_SIN: Layer = (319, 0)
XS_SIN2: Layer = (305, 0)
XS_SI_SLAB: Layer = (313, 0)
XS_VIA1: Layer = (308, 0)
XS_VIA2: Layer = (310, 0)
XS_VIAC: Layer = (303, 0)
class Config:
frozen = True
extra = "forbid"
LAYER = LayerMap()
[2]:
from pydantic import BaseModel
from gdsfactory.types import Layer
class LayerMap(BaseModel):
WG: Layer = (1, 0)
DEVREC: Layer = (68, 0)
PORT: Layer = (1, 10)
PORTE: Layer = (1, 11)
LABEL: Layer = (201, 0)
LABEL_INSTANCES: Layer = (206, 0)
LABEL_SETTINGS: Layer = (202, 0)
LUMERICAL: Layer = (733, 0)
M1: Layer = (41, 0)
M2: Layer = (45, 0)
M3: Layer = (49, 0)
N: Layer = (20, 0)
NP: Layer = (22, 0)
NPP: Layer = (24, 0)
OXIDE_ETCH: Layer = (6, 0)
P: Layer = (21, 0)
PDPP: Layer = (27, 0)
PP: Layer = (23, 0)
PPP: Layer = (25, 0)
PinRec: Layer = (1, 10)
PinRecM: Layer = (1, 11)
SHALLOWETCH: Layer = (2, 6)
SILICIDE: Layer = (39, 0)
SIM_REGION: Layer = (100, 0)
SITILES: Layer = (190, 0)
SLAB150: Layer = (2, 0)
SLAB150CLAD: Layer = (2, 9)
SLAB90: Layer = (3, 0)
SLAB90CLAD: Layer = (3, 1)
SOURCE: Layer = (110, 0)
TE: Layer = (203, 0)
TEXT: Layer = (66, 0)
TM: Layer = (204, 0)
Text: Layer = (66, 0)
VIA1: Layer = (44, 0)
VIA2: Layer = (43, 0)
VIAC: Layer = (40, 0)
WGCLAD: Layer = (111, 0)
WGN: Layer = (34, 0)
WGNCLAD: Layer = (36, 0)
class Config:
frozen = True
extra = "forbid"
LAYER = LayerMap()
There are some default layers in some generic components and cross_sections, that it may be convenient adding.
Layer |
Purpose |
|---|---|
DEVREC |
device recognition layer. For connectivity checks. |
PORT |
optical port pins. For connectivity checks. |
PORTE |
electrical port pins. For connectivity checks. |
SHOW_PORTS |
add port pin markers when |
LABEL |
for adding labels to grating couplers for automatic testing. |
LABEL_INSTANCE |
for adding instance labels on |
TE |
for TE polarization fiber marker. |
TM |
for TM polarization fiber marker. |
class LayersConvenient(BaseModel):
DEVREC: Layer = (68, 0)
PORT: Layer = (1, 10) # PinRec optical
PORTE: Layer = (1, 11) # PinRec electrical
SHOW_PORTS: Layer = (1, 12)
LABEL: Layer = (201, 0)
LABEL_INSTANCE: Layer = (66, 0)
TE: Layer = (203, 0)
TM: Layer = (204, 0)
cross_sections#
You can create a CrossSection from scratch or you can customize the cross_section functions in gf.cross_section
[3]:
import gdsfactory as gf
strip2 = gf.partial(gf.cross_section.strip, layer=(2, 0))
[4]:
c = gf.components.straight(cross_section=strip2)
c
[4]:
[5]:
import gdsfactory as gf
pin = gf.partial(
gf.cross_section.strip,
sections=(
gf.tech.Section(width=2, layer=gf.LAYER.N, offset=+1),
gf.tech.Section(width=2, layer=gf.LAYER.P, offset=-1),
),
)
c = gf.components.straight(cross_section=pin)
c
[5]:
[6]:
strip_wide = gf.partial(gf.cross_section.strip, width=3)
[7]:
strip = gf.partial(
gf.cross_section.strip, auto_widen=True
) # auto_widen tapers to wider waveguides for lower loss in long straight sections.
[8]:
cross_sections = dict(strip_wide=strip_wide, pin=pin, strip=strip)
cells#
Cells are functions that return Components. They are parametrized and accept also cells as parameters, so you can build many levels of complexity. Cells are also known as PCells or parametric cells.
You can customize the function default arguments easily thanks to functools.partial Lets customize the default arguments of a library of cells.
For example, you can make some wide MMIs for a particular technology. Lets say the best MMI width you found it to be 9um.
[9]:
import gdsfactory as gf
mmi1x2 = gf.partial(gf.components.mmi1x2, width_mmi=9)
mmi2x2 = gf.partial(gf.components.mmi2x2, width_mmi=9)
cells = dict(mmi1x2=mmi1x2, mmi2x2=mmi2x2)
LayerSpec, ComponentSpec, CrossSectionSpec#
When you regiser Layers, ComponentFactories (cells) and CrossSectionFactories (cross_sections), you can access them by a string.
LayerSpec#
You can access layers from the active Pdk using the layer name or a tuple/list of two numbers.
[10]:
pdk1 = gf.Pdk(
name="fab1",
layers=LAYER.dict(),
cross_sections=cross_sections,
cells=cells,
base_pdk=gf.pdk.GENERIC,
sparameters_path=gf.config.sparameters_path,
layer_colors=gf.layers.LAYER_COLORS
)
pdk1.activate()
[11]:
pdk1.get_layer("WG")
[11]:
(1, 0)
[12]:
pdk1.get_layer([1, 0])
[12]:
[1, 0]
CrossSectionSpec#
You can access cross_sections from the pdk from the cross_section name, or using a dict to customize the CrossSection
[13]:
pdk1.get_cross_section("pin")
[13]:
CrossSection(layer='WG', width=0.5, offset=0.0, radius=10.0, width_wide=None, auto_widen=False, auto_widen_minimum_length=200.0, taper_length=10.0, bbox_layers=[], bbox_offsets=[], cladding_layers=['DEVREC'], cladding_offsets=(0.0,), sections=[Section(width=2.0, offset=1.0, layer=(20, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False), Section(width=2.0, offset=-1.0, layer=(21, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False)], port_names=('o1', 'o2'), port_types=('optical', 'optical'), min_length=0.01, start_straight_length=0.01, end_straight_length=0.01, snap_to_grid=None, decorator=None, add_pins=functools.partial(<function add_pins_siepic at 0x7f3c13e48670>, pin_length=0.002), add_bbox=<function add_bbox_siepic at 0x7f3c13e48700>, info={}, name=None)
[14]:
cross_section_spec_string = "pin"
gf.components.straight(cross_section=cross_section_spec_string)
[14]:
[15]:
cross_section_spec_dict = dict(cross_section="pin", settings=dict(width=2))
print(pdk1.get_cross_section(cross_section_spec_dict))
wg_pin = gf.components.straight(cross_section=cross_section_spec_dict)
wg_pin
layer='WG' width=2.0 offset=0.0 radius=10.0 width_wide=None auto_widen=False auto_widen_minimum_length=200.0 taper_length=10.0 bbox_layers=[] bbox_offsets=[] cladding_layers=['DEVREC'] cladding_offsets=(0.0,) sections=[Section(width=2.0, offset=1.0, layer=(20, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False), Section(width=2.0, offset=-1.0, layer=(21, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False)] port_names=('o1', 'o2') port_types=('optical', 'optical') min_length=0.01 start_straight_length=0.01 end_straight_length=0.01 snap_to_grid=None decorator=None add_pins=functools.partial(<function add_pins_siepic at 0x7f3c13e48670>, pin_length=0.002) add_bbox=<function add_bbox_siepic at 0x7f3c13e48700> info={} name=None
[15]:
ComponentSpec#
You can get Component from the pdk using the cell name (string) or a dict.
[16]:
pdk1.get_component("mmi1x2")
[16]:
[17]:
pdk1.get_component(dict(component="mmi1x2", settings=dict(length_mmi=10)))
[17]:
Now you can define PDKs for different Fabs
FabA#
FabA only has one waveguide layer available that is defined in GDS layer (30, 0)
The waveguide traces are 2um wide.
[18]:
import gdsfactory as gf
from gdsfactory.types import Layer, LayerColors, LayerColor, LayerStack, LayerLevel
from pydantic import BaseModel
nm = 1e-3
class LayerMap(BaseModel):
WG: Layer = (34, 0)
SLAB150: Layer = (2, 0)
DEVREC: Layer = (68, 0)
PORT: Layer = (1, 10)
PORTE: Layer = (1, 11)
TE: Layer = (203, 0)
TM: Layer = (204, 0)
TEXT: Layer = (66, 0)
LAYER = LayerMap()
layer_colors = dict(
WG=LayerColor(gds_layer=34, gds_datatype=0, color='gold'),
SLAB150=LayerColor(gds_layer=2, gds_datatype=0, color='red'),
TE=LayerColor(gds_layer=203, gds_datatype=0, color='green'),
)
LAYER_COLORS = LayerColors(layers=layer_colors)
def get_layer_stack_faba(thickness_wg: float = 500 * nm, thickness_slab:float=150*nm) -> LayerStack:
"""Returns fabA LayerStack"""
## TODO: Translate xml in lumerical process file
return LayerStack(
layers=dict(
strip=LayerLevel(
layer=LAYER.WG,
thickness=thickness_wg,
zmin=0.0,
material="si",
),
strip2=LayerLevel(
layer=LAYER.SLAB150,
thickness=thickness_slab,
zmin=0.0,
material="si",
),
)
)
LAYER_STACK = get_layer_stack_faba()
WIDTH = 2
# Specify a cross_section to use
strip = gf.partial(gf.cross_section.cross_section, width=WIDTH, layer=LAYER.WG)
mmi1x2 = gf.partial(
gf.components.mmi1x2,
width=WIDTH,
width_taper=WIDTH,
width_mmi=3 * WIDTH,
cross_section=strip,
)
generic_pdk = gf.pdk.GENERIC
fab_a = gf.Pdk(
name="Fab_A",
cells=dict(mmi1x2=mmi1x2),
cross_sections=dict(strip=strip),
layers=LAYER.dict(),
base_pdk=generic_pdk,
sparameters_path=gf.config.sparameters_path,
layer_colors=LAYER_COLORS,
layer_stack=LAYER_STACK
)
fab_a.activate()
gc = gf.partial(
gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip
)
c = gf.components.mzi()
c_gc = gf.routing.add_fiber_array(component=c, grating_coupler=gc, with_loopback=False)
c_gc
[18]:
[19]:
c = c_gc.to_3d()
c.show()
[19]:
FabB#
FabB has photonic waveguides that require rectangular cladding layers to avoid dopants
Lets say that the waveguides are defined in layer (2, 0) and are 0.3um wide, 1um thick
[20]:
import gdsfactory as gf
from gdsfactory.types import Layer, LayerColor, LayerColors, LayerStack, LayerLevel
from pydantic import BaseModel
nm = 1e-3
class LayerMap(BaseModel):
WG: Layer = (2, 0)
SLAB150: Layer = (3, 0)
DEVREC: Layer = (68, 0)
PORT: Layer = (1, 10)
PORTE: Layer = (1, 11)
TE: Layer = (203, 0)
TM: Layer = (204, 0)
TEXT: Layer = (66, 0)
LABEL: Layer = (201, 0)
DOPING_BLOCK1: Layer = (61, 0)
DOPING_BLOCK2: Layer = (62, 0)
LAYER = LayerMap()
layer_colors = dict(
WG=LayerColor(gds_layer=2, gds_datatype=0, color='red'),
SLAB150=LayerColor(gds_layer=3, gds_datatype=0, color='blue'),
TE=LayerColor(gds_layer=203, gds_datatype=0, color='green'),
PORT=LayerColor(gds_layer=1, gds_datatype=10, color='green', alpha=0),
DOPING_BLOCK1=LayerColor(gds_layer=61, gds_datatype=0, color='green', alpha=0),
DOPING_BLOCK2=LayerColor(gds_layer=62, gds_datatype=0, color='green', alpha=0),
)
LAYER_COLORS = LayerColors(layers=layer_colors)
def get_layer_stack_fab_b(thickness_wg: float = 1000 * nm, thickness_slab:float=150*nm) -> LayerStack:
"""Returns fabA LayerStack"""
## TODO: Translate xml in lumerical process file
return LayerStack(
layers=dict(
strip=LayerLevel(
layer=LAYER.WG,
thickness=thickness_wg,
zmin=0.0,
material="si",
),
strip2=LayerLevel(
layer=LAYER.SLAB150,
thickness=thickness_slab,
zmin=0.0,
material="si",
),
)
)
LAYER_STACK = get_layer_stack_fab_b()
WIDTH = 0.3
BBOX_LAYERS = (LAYER.DOPING_BLOCK1, LAYER.DOPING_BLOCK2)
BBOX_OFFSETS = (3, 3)
# use cladding_layers and cladding_offsets if the foundry prefers conformal blocking doping layers instead of squared
# bbox_layers and bbox_offsets makes rectangular waveguides.
strip = gf.partial(
gf.cross_section.cross_section,
width=WIDTH,
layer=LAYER.WG,
#bbox_layers=BBOX_LAYERS,
#bbox_offsets=BBOX_OFFSETS,
cladding_layers=BBOX_LAYERS,
cladding_offsets=BBOX_OFFSETS,
)
straight = gf.partial(gf.components.straight, cross_section=strip)
bend_euler = gf.partial(gf.components.bend_euler, cross_section=strip)
mmi1x2 = gf.partial(
gf.components.mmi1x2,
cross_section=strip,
width=WIDTH,
width_taper=WIDTH,
width_mmi=4 * WIDTH,
)
mzi = gf.partial(gf.components.mzi, cross_section=strip, splitter=mmi1x2)
gc = gf.partial(
gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip
)
cells = dict(
gc=gc,
mzi=mzi,
mmi1x2=mmi1x2,
bend_euler=bend_euler,
straight=straight,
taper=gf.components.taper,
)
cross_sections = dict(strip=strip)
pdk = gf.Pdk(
name="fab_b",
cells=cells,
cross_sections=cross_sections,
layers=LAYER.dict(),
sparameters_path=gf.config.sparameters_path,
layer_colors=LAYER_COLORS,
layer_stack=LAYER_STACK
)
pdk.activate()
c = mzi()
wg_gc = gf.routing.add_fiber_array(component=c, grating_coupler=gc, cross_section=strip, with_loopback=False)
wg_gc
[20]:
[21]:
c = wg_gc.to_3d()
c.show()
[21]:
FabC#
Lets assume that fab C has similar technology to the generic PDK in gdsfactory and that you just want to remap some layers, and adjust the widths.
[22]:
from typing import Callable, Dict, Optional, Tuple
from pydantic import BaseModel
import gdsfactory as gf
from gdsfactory.add_pins import add_pin_rectangle_inside
from gdsfactory.component import Component
from gdsfactory.cross_section import cross_section
from gdsfactory.tech import LayerLevel, LayerStack, Tech
from gdsfactory.types import Layer, LayerColor, LayerColors
nm = 1e-3
class LayerMap(BaseModel):
WG: Layer = (10, 1)
WG_CLAD: Layer = (10, 2)
WGN: Layer = (34, 0)
WGN_CLAD: Layer = (36, 0)
SLAB150: Layer = (2, 0)
DEVREC: Layer = (68, 0)
PORT: Layer = (1, 10)
PORTE: Layer = (1, 11)
TE: Layer = (203, 0)
TM: Layer = (204, 0)
TEXT: Layer = (66, 0)
LABEL: Layer = (201, 0)
LAYER = LayerMap()
WIDTH_NITRIDE_OBAND = 0.9
WIDTH_NITRIDE_CBAND = 1.0
PORT_TYPE_TO_LAYER = dict(optical=(100, 0))
# This is something you usually define in klayout
layer_colors = dict(
WG=LayerColor(gds_layer=10, gds_datatype=1, color='black'),
SLAB150=LayerColor(gds_layer=2, gds_datatype=0, color='blue'),
WGN=LayerColor(gds_layer=34, gds_datatype=0, color='orange'),
WGN_CLAD=LayerColor(gds_layer=36, gds_datatype=0, color='blue', alpha=0),
TE=LayerColor(gds_layer=203, gds_datatype=0, color='green'),
PORT=LayerColor(gds_layer=1, gds_datatype=10, color='green', alpha=0),
DOPING_BLOCK1=LayerColor(gds_layer=61, gds_datatype=0, color='green', alpha=0),
DOPING_BLOCK2=LayerColor(gds_layer=62, gds_datatype=0, color='green', alpha=0),
)
LAYER_COLORS = LayerColors(layers=layer_colors)
def get_layer_stack_fab_c(thickness_wg: float = 350.0*nm) -> LayerStack:
"""Returns generic LayerStack"""
return LayerStack(
layers=dict(
core=LayerLevel(
layer=LAYER.WGN,
thickness=thickness_wg,
zmin=0,
),
clad=LayerLevel(layer=LAYER.WGN_CLAD,
)
))
LAYER_STACK = get_layer_stack_fab_c()
def add_pins(
component: Component,
function: Callable = add_pin_rectangle_inside,
pin_length: float = 0.5,
port_layer: Layer = LAYER.PORT,
**kwargs,
) -> Component:
"""Add Pin port markers.
Args:
component: to add ports.
function:
pin_length:
port_layer:
function: kwargs.
"""
for p in component.ports.values():
function(
component=component,
port=p,
layer=port_layer,
layer_label=port_layer,
pin_length=pin_length,
**kwargs,
)
return component
# cross_sections
bbox_layers = [LAYER.WGN_CLAD]
bbox_offsets = [3]
# Nitride Cband
xs_nc = gf.partial(
cross_section,
width=WIDTH_NITRIDE_CBAND,
layer=LAYER.WGN,
bbox_layers=bbox_layers,
bbox_offsets=bbox_offsets,
add_pins=add_pins,
)
# Nitride Oband
xs_no = gf.partial(
cross_section,
width=WIDTH_NITRIDE_OBAND,
layer=LAYER.WGN,
bbox_layers=bbox_layers,
bbox_offsets=bbox_offsets,
add_pins=add_pins,
)
cross_sections = dict(xs_nc=xs_nc, xs_no=xs_no, strip=xs_nc)
# LEAF cells have pins
mmi1x2_nc = gf.partial(
gf.components.mmi1x2,
width=WIDTH_NITRIDE_CBAND,
cross_section=xs_nc,
)
mmi1x2_no = gf.partial(
gf.components.mmi1x2,
width=WIDTH_NITRIDE_OBAND,
cross_section=xs_no,
)
bend_euler_nc = gf.partial(
gf.components.bend_euler,
cross_section=xs_nc,
)
straight_nc = gf.partial(
gf.components.straight,
cross_section=xs_nc,
)
bend_euler_no = gf.partial(
gf.components.bend_euler,
cross_section=xs_no,
)
straight_no = gf.partial(
gf.components.straight,
cross_section=xs_no,
)
gc_nc = gf.partial(
gf.components.grating_coupler_elliptical_te,
grating_line_width=0.6,
layer=LAYER.WGN,
cross_section=xs_nc,
)
# HIERARCHICAL cells are made of leaf cells
mzi_nc = gf.partial(
gf.components.mzi,
cross_section=xs_nc,
splitter=mmi1x2_nc,
straight=straight_nc,
bend=bend_euler_nc,
)
mzi_no = gf.partial(
gf.components.mzi,
cross_section=xs_no,
splitter=mmi1x2_no,
straight=straight_no,
bend=bend_euler_no,
)
cells = dict(
mmi1x2_nc=mmi1x2_nc,
mmi1x2_no=mmi1x2_no,
bend_euler_nc=bend_euler_nc,
bend_euler_no=bend_euler_no,
straight_nc=straight_nc,
straight_no=straight_no,
gc_nc=gc_nc,
mzi_nc=mzi_nc,
mzi_no=mzi_no,
)
pdk = gf.Pdk(
name="fab_c",
cells=cells,
cross_sections=cross_sections,
layers=LAYER.dict(),
sparameters_path=gf.config.sparameters_path,
layer_colors=LAYER_COLORS,
layer_stack=LAYER_STACK
)
pdk.activate()
[23]:
mzi = mzi_nc()
mzi_gc = gf.routing.add_fiber_single(
component=mzi,
grating_coupler=gc_nc,
cross_section=xs_nc,
optical_routing_type=1,
straight=straight_nc,
bend=bend_euler_nc,
)
mzi_gc
[23]:
[24]:
c = mzi_gc.to_3d()
c.show()
[24]:
[25]:
ls = get_layer_stack_fab_c()
PDK Testing#
To make sure all your PDK Pcells produce the components that you want, it’s important to test your PDK.
As you write your own cell functions you want to make sure you do not break them later on. You can write tests to avoid unwanted regressions.
Make sure you create a test like this an name it with test_ prefix so Pytest can find it.
[26]:
"""This code tests all your cells in the PDK
it will test 3 things:
1. difftest: will test the GDS geometry of a new GDS compared to a reference. Thanks to Klayout fast booleans.add()
2. settings test: will compare the settings in YAML with a reference YAML file.add()
3. ensure ports are on grid, to avoid port snapping errors that can create 1nm gaps later on when you build circuits.
"""
import pathlib
import pytest
from pytest_regressions.data_regression import DataRegressionFixture
import gdsfactory as gf
from gdsfactory.difftest import difftest
try:
dirpath = pathlib.Path(__file__).absolute().with_suffix(".gds")
except Exception:
dirpath = pathlib.Path.cwd()
component_names = list(pdk.cells.keys())
factory = pdk.cells
@pytest.fixture(params=component_names, scope="function")
def component_name(request) -> str:
return request.param
def test_gds(component_name: str) -> None:
"""Avoid regressions in GDS names, shapes and layers.
Runs XOR and computes the area."""
component = factory[component_name]()
test_name = f"fabc_{component_name}"
difftest(component, test_name=test_name, dirpath=dirpath)
def test_settings(component_name: str, data_regression: DataRegressionFixture) -> None:
"""Avoid regressions in component settings and ports."""
component = factory[component_name]()
data_regression.check(component.to_dict())
def test_assert_ports_on_grid(component_name: str):
"""Ensures all ports are on grid to avoid 1nm gaps"""
component = factory[component_name]()
component.assert_ports_on_grid()
[ ]: